for loops and range · nested loops · while loops · break and continue
A loop runs the same block of code repeatedly. Python gives you two kinds: for loops and
while loops. Loops pair especially well with strings, since a string can be any length, and a loop
doesn't care how many times it has to run. Together, loops and strings let you solve far more interesting problems than one-shot, straight-line code ever could.
Think of something you repeat in real life. Is it something you repeat a known number of times (like doing 20 push-ups), or something you repeat until a condition is met (like shuffling cards until they feel mixed)? Describe one of each.
A for loop is the tool for when you already know how many passes you need, or you know exactly what
you're looping over. Python's range is the most common thing to loop over.
for, often something like x, is the loop variable (also called the looping variable).range() is a function which generates a sequence of numbers from 0 up to (but not including) the specified number.range(4) is exclusive. It produces the sequence 0, 1, 2, 3, up to but not including 4.for LOOPVAR in range(N):
BODY
for x in range(4):
print(x)
Line 1 assigns the next value to x, then Python enters the body. x's new value only becomes visible once the caret has moved past line 1, and print(x)'s output only becomes visible once the caret moves past line 2, back up to check the range again.
total is a running total (also called a tally): a variable that carries a value across passes, updated a little more on every one. Watch it grow.
n is 4, so range(n+1) is range(5): five passes, x = 0, 1, 2, 3, 4.
sumToN(n) uses range(n+1) instead of range(n). What is it trying to include that range(n) alone would leave out?
def sumToN(n):
total = 0
for x in range(n):
total += x
return total
With this change, what does sumToN(4) return?
10, exactly the same6, since it would miss the 40
With two arguments, range treats the first as the start value (inclusive) and the second as the end value (still exclusive).
range(1, 5) # 1, 2, 3, 4
factorial(n) should multiply the integers from 1 to n. This version copies sumToN and swaps += for *=. Predict the output, then run it.
def factorial(n):
total = 0
for x in range(n+1):
total *= x
return total
print(factorial(4))
range(n+1) starting at the default 0 includes 0 itself. That was fine for sumToN, adding 0 changes nothing, but for a product, multiplying by 0 even once wipes out the whole running total. The fix: start the range at 1 instead of 0, and start total at 1 too, since it's the identity value for multiplication.
0 (adding 0 is a no-op). A running product starts at 1 (multiplying by 1 is a no-op).
total now starts at 1, and range(1, n+1) skips the 0 entirely.
range(1, 5): four passes, x = 1, 2, 3, 4.
for i in range(2, 9): print(i)for i in range(5, 8): print(i)for i in range(5, 9): print(i)for i in range(2, 8): print(i)
A third argument is the step: the amount added to the loop variable on every pass. Without it, the step defaults to 1.
range(20, 40, 5) # 20, 25, 30, 35
Each pass, x jumps by 5 instead of the default 1.
To loop backwards over 12, 11, 10, a first attempt, range(12, 10, -1), almost works: it only prints 12 and 11, because the end is still exclusive. The fix drops the end to 9, one past the last value we want.
y down to x inclusively: range(y, x-1, -1).
for i in range(5, 13, 3): print(i)for i in range(6, 13, 3): print(i)for i in range(3, 6, 15): print(i)for i in range(6, 12, 3): print(i)for i in range(15, 12, -1): print(i)for i in range(16, 11, -1): print(i)for i in range(15, 11, -1): print(i)for i in range(16, 12, -1): print(i)for LOOPVAR in range(N): assigns the loop variable a new value each pass, then runs the body. range(N) is exclusive of N.range(start, end) and range(start, end, step) control where the loop begins, ends, and how far it jumps each pass.0 for addition, 1 for multiplication.y down to x inclusively, use range(y, x-1, -1).
Just like an if can nest inside another if, a for loop can nest inside another for loop. The two loop variables
update at very different rates, and seeing exactly how is the whole point of this section.
3 × 3 = 9 times total.for x in range(3):
for y in range(3):
BODY
Predict the full output, in order. How many lines total, and what are the first three rows?
print('x', 'y')
for x in range(3):
for y in range(3):
print(x, y)
Watch how often each box updates. y gets reassigned on nearly every pass. x only changes once the entire inner loop has finished, and its old value keeps showing until then, since Python hasn't reassigned it yet.
3 times total: once each time the entire inner loop finishes.This is why nested loops are so useful for grids, tables, and anything with rows and columns: the outer variable tracks the row, the inner variable sweeps across every column before the row advances.
for j in range(2,9,6): for i in range(30,36,2): print(i, j)for i in range(30,36,2): for j in range(2,9,6): print(i, j)for i in range(2,9,6): for j in range(30,36,2): print(i, j)for j in range(30,36,2): for i in range(2,9,6): print(i, j)The inner loop's range doesn't have to be fixed. It can use the outer loop variable, so each outer pass gets a differently sized inner loop.
for x in range(4):
for y in range(x+1):
print(x, y)
range(x+1) is re-evaluated fresh at the start of every inner loop, using whatever x is at that moment. When x is 0, the inner loop only has one pass.
With y looping over range(x) instead of range(x+1), the very first outer pass (x is 0) contributes zero rows to the output. What's the real reason?
0.range(0) is a valid range with zero values in it, so the inner loop simply runs zero passes.print statement is unreachable when x is 0.range(0) raises an error that Python silently ignores.range(0), simply produces zero passes. Nothing crashes, nothing runs.
Sometimes you don't know how many passes you'll need before you start. A while loop keeps running so long as a condition is
True, re-checking it before every pass, closer to how an if statement behaves than a for loop does.
for when you already know how many passes you need, or exactly what you're looping over.while when the number of passes depends on something you can't know in advance, like user input.while loop re-checks its test before every single pass, and stops the moment the test is False.while TEST:
BODY
We can't know how many passes this needs until we see what the user types. For this trace, imagine the user enters 30, then 40, then 50.
120 > 100, so the loop stops before a fourth pass.
A program needs to keep asking the user to guess a secret number until they finally guess correctly. Which loop is the right tool, and why?
for loop, since you always know range(10) will work for guessing games.while loop, since the number of guesses needed can't be known before the program runs.for loop, since guessing is a kind of counting.nextNumber = int(input(...)) converts the typed text to an int. What happens if the user types a float, like 3.5, instead of a whole number?
Run it and enter whatever numbers you like. Try entering a float. Try entering a negative number. Try entering text that isn't a number at all, and see what happens.
while TEST: loop keeps running as long as TEST is True, re-checking before every pass.while-loop bug: it produces an infinite loop.
break and continue both alter a loop's normal flow from inside the body. Used well, they can be very handy. Used too often,
they make code harder to follow, so use them sparingly.
break inside a loop body exits the entire loop right away, skipping any remaining lines in that pass and every future pass.while loop, when you can only tell it's time to stop after you're already inside the loop body.while True: means "loop forever", an infinite loop, unless something inside the body eventually breaks out of it.while True:
BODY
if TEST:
break
Imagine the user enters 30, then 40, then 0. Watch what happens to line 7, total += nextNumber, on the last pass.
On the last pass, line 7 never runs. break jumps straight to line 8.
You can write this without break by looping on nextNumber != 0 directly. That means nextNumber needs a starting value that isn't 0 before the loop even begins, and None is a natural choice.
Both versions are common. Understand both.
total = 0
nextNumber = None
while nextNumber != 0:
nextNumber = int(input('Enter next number: '))
if nextNumber != 0:
total += nextNumber
total = 0
while True:
nextNumber = int(input('Enter next: '))
if nextNumber == 20:
break
total += nextNumber
print(total)
If the user enters 5, 10, 15, then 20, what does this print?
03050
continue is similar to break, but gentler: it exits only the current pass, not the whole loop. Python jumps straight back to the top of the loop and carries on with the next pass, as if the rest of that pass's body was skipped.
for i in range(12):
if i % 2 == 0:
continue
print(i)
When i is even, continue jumps straight back to line 1 and line 4 never runs for that pass. When i is odd, the if body is skipped instead, and line 4 runs normally.
In the previous example, what happens if you use break instead of continue?
The continue version above shows how it works, but it's not the clearest way to write this. Only reach for continue when it genuinely makes code easier to read, which is rare.
for i in range(12):
if i % 2 == 1:
print(i)
for i in range(1, 12, 2):
print(i)
Write a program that adds up numbers the user enters, one at a time, stopping as soon as the running total goes over 50. Then print how many numbers it took.
This could be written with a while loop, or with a for loop and a break. Pick whichever feels like the right tool, you'll need to defend your choice next.
Did you reach for a while loop, or a for loop with a break? Why did that feel like the right tool for this task, and could the other approach have worked too?
break exits the entire loop immediately, often paired with while True: to build a loop that stops from the inside.continue skips only the rest of the current pass and jumps back to the top of the loop for the next one.step in range, often reads more clearly than either one.for LOOPVAR in range(...): for a known number of passeswhile TEST: for an unknown number, re-checked every pass.break exits a loop entirely. continue skips only the current pass. Both are optional, and both are easy to overuse.
Write the function isPrime(n) that takes a possibly-negative integer n and returns True if n is prime, and False otherwise.
The starter code on the next slide includes a set of tests. A loop is the natural tool for checking whether any smaller number divides evenly into n.
Write the function nthPrime(n) that takes a non-negative integer n and returns the nth prime number. 2 is the 0th prime number, 3 is the 1st prime number, and so on.
Write the function reverseNumber(n) that takes an integer n and returns an integer with its digits in the reverse order of the digits in n.
Write the function mostFrequentDigit(n) that takes a possibly-negative integer n and returns the digit from 0 to 9 that occurs most frequently in n. Ties go to the larger digit.
Replace the placeholder return 42 with real logic, then run main(). Silence means every assert in testMostFrequentDigit passed; an AssertionError tells you exactly which case still fails.
Write the function hasConsecutiveDigits(n) that takes a possibly-negative integer n and returns True if that number contains two consecutive digits that are the same, and False otherwise.
For example, 1223 has two consecutive 2's, but 1232 does not. A loop that compares each digit to the one right before it will catch this.
Replace the placeholder return 42 with real logic, then run main(). Silence means every assert in testHasConsecutiveDigits passed; an AssertionError tells you exactly which case still fails.